package com.tjc.cloud.util.mongo.query

import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import reactor.core.publisher.Mono
import reactor.core.publisher.toMono
import java.lang.Exception
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor


annotation class Query(val operator: Operator, val key: String = "")

enum class Operator(var handler: (criteria: Criteria, value: Any) -> Criteria) {
    Equals({ c, v -> c.`is`(v) }),
    Like({ c, v -> c.regex(v as String) }),
    Ge({ c, v -> c.gte(v) }),
    Le({ c, v -> c.lte(v) }),
    Gt({ c, v -> c.gt(v) }),
    Lt({ c, v -> c.lt(v) }),
    In({ c, v ->
        when (v) {
            is Collection<*> -> c.`in`(v)
            is Array<*> -> c.`in`(*v)
            else -> throw Exception("${c.key}字段必须是集合类型才能使用In操作符")
        }
    })
}

fun org.springframework.data.mongodb.core.query.Query.queryModel(model: Any) = Mono.just(model).flatMap { model ->
    Mono.justOrEmpty(model::class.primaryConstructor).flatMapIterable { it?.parameters ?: listOf() }
            .filter {
                it.findAnnotation<Query>() != null
            }.filter { p ->
                model::class.memberProperties.find { it.name == p.name }?.call(model) != null
            }

            .map { p ->
                val v = model::class.memberProperties.find { it.name == p.name }!!.call(model)
                val o = p.findAnnotation<Query>()!!
                arrayOf(if (o.key == "") p.name!! else o.key, o.operator, v)
//                o.operator.handler.invoke(if (o.key == "") p.name!! else o.key, v!!)
            }
            .groupBy(Array<*>::first)
            .flatMap {
                it.reduce(Criteria()) { t, u ->
                    val c = if (t.key != u.component1())
                        Criteria(u.component1() as String)
                    else t
                    (u.component2() as Operator).handler.invoke(c, u.component3()!!)
                }
            }
            .reduce(this) { a, s ->
                a.addCriteria(s)
            }
}

inline fun <reified T> ReactiveMongoTemplate.page(query: org.springframework.data.mongodb.core.query.Query,
                                                  page: Pageable,
                                                  collectionName: String? = null): Mono<Page<T>> =
        if (collectionName == null)
            this.count(query.with(page), T::class.java)
        else {
            this.count(query.with(page), T::class.java, collectionName)
        }
//        .flatMap {
//            it.toMono().thenReturn(it)
//        }
        .zipWith(
                if (collectionName == null)
                    this.find(query.with(page), T::class.java)
                else {
                    this.find(query.with(page), T::class.java, collectionName)
                }
                        .collectList()) { count, list ->
            PageImpl(list, page, count)
        }

fun main(args: Array<String>) {
    data class QueryModel(
            @Query(Operator.Le, key = "name")
            val a: Int? = null,
            @Query(Operator.Ge, key = "name")
            var b: Int)
    org.springframework.data.mongodb.core.query.Query()
            .queryModel(QueryModel(10, 20)).subscribe {
                println(it.queryObject)
            }
}